網頁與伺服器的溝通


Posted by ericcch24 on 2020-10-16

用 node.js 呼叫 API 與在網頁上呼叫的根本差異

  • 在網頁上呼叫 API,是透過瀏覽器發送 request 與接收 response,而瀏覽器會加上一些特殊的資訊或是限制某些行為,反之在 node.js 則沒有這些額外東西。

傳送資料的方式

表單 form -- 最基本的方式

  • action 是要提交資料的頁面,瀏覽器發送post的 request 到 action(新的頁面),而瀏覽器會直接 render action 傳回來的 response,此時網頁在提交 request 後會換頁。
  • 比起與 server 的資料交換,此方式比較像是要帶上一些參數的資料到 action 這個頁面。
  • 與 JS 無關。

AJAX (Asynchronous JavaScript And XML)

  • 可以讓我們在瀏覽器上透過 Javascript 交換資料的技術,其重點在於==非同步的處理方式==,可以讓 Javascript 發送 request 之後,不等 reponse 回傳就繼續執行接下來的程式。而在 Response 回傳之後,就可以透過 Callback Function把回傳的資料帶進來。
  • 與表單發送資料的差異是 Ajax 可以在拿取資料的時候,將回傳的結果透過 Javascript 來處理顯示在頁面的形式,而不是像表單直接更新整個頁面。
    ```javascript=
    const request = new XMLHttpRequest();
    // 類似我要發一個新的 request 的意思
    request.onload = function() {
    // request 拿到結果時觸發 onload
    if (request.status >= 200 && request.status < 400) {
    // 表示 request 成功
    console.log(request.responseText);
    // 得到 response
    // 不一定每個 response 都有 responseText,有時候會是空物件
    } else {
    console.log('err');
    }
    }

// 也可以用監聽事件老方法
request.addEventListener('load', function() {
if (request.status >= 200 && request.status < 400) {
console.log(request.responseText);
} else {
console.log('err');
}
})

request.onerror = function() {
// request 有錯誤的時候
console.log('error');
}

request.open('GET', 'https://reqres.in/api/users', true);
// 發 request 的 method、位置、要不要非同步處理
// (同步的話會等待 response 回傳然後整個瀏覽器卡住)

request.send(); // 把 request 送出

* 從伺服器資源抓取資料後,下一步就是送出資料給伺服器,有些情況下,傳輸資料可能會影響返回的資料,例如為了取得某部門的員工,Ajax 請求可以將部門識別字送到伺服器。其他情況下,發送的資料需通過伺服器的驗證,例如使用者名稱是否存在;或觸發伺服器回應,例如張貼評論置留言板上。
* 將資料作為請求的一部份送給伺服器有兩種方法:
  1. ==將資料附加到 URL 後==:以「名稱 = 值」為資料結構配對,然後配對間用「&」隔開,為保證請求資料的安全性,可將其封裝至 ==`encodeURIComponent()`== 呼叫中
        ```javascript=
        ajax.open('GET', 'http://www.example.com/somepage.php?id=' +     encodeURIComponent(id), true);
  1. 把資料作為 send() 方法(代替 null)的唯一參數
       var data = 'email=' + encodeURIComponent(email) + '&password=' + encodeURIComponent(password);
       ajax.open('POST', 'http://www.example.com/somepage.php', true);
       ajax.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');   //指定合適的資料編碼
       ajax.send(data);
    
    參考資料:DOMO MEMO

Same Origin Policy 同源政策

  • 當現在這個網站的跟你要呼叫的 API 的網站「不同源」的時候,瀏覽器一樣會幫你發 Request,但是會把 Response 給擋下來,不讓你的 JavaScript 拿到並且傳回錯誤,這是瀏覽器為了安全性考量的限制,node.js 就完全沒有。
  • 什麼是不同源呢?只要是 Domain 不一樣就是不同源,或者是一個用 http 一個用 https 也是不同源,端口號不一樣也是不同源。
  • 「跨網域」這個問題基本上只會在瀏覽器發生,因為安全性的關係。這是我們第四週在自己的電腦上用自己寫的程式發 request,跟第八週透過瀏覽器來發 request 最大最大的差別。
    參考資料:輕鬆理解 Ajax 與跨來源請求
    ### CORS: Cross-Origin Resource Sharing 跨來源資源共享
  • 如果要在不同 origin 之間傳輸資料的話,也就是想開啟跨來源 HTTP 請求的話,Server 必須在 Response 的 Header 裡面加上==Access-Control-Allow-Origin==。
  • 當瀏覽器收到 Response 之後,會先檢查==Access-Control-Allow-Origin==裡面的內容,如果裡面有包含現在這個發起 Request 的 Origin 的話,就會允許通過,讓程式順利接收到 Response。
  • 除了這個 Header 以外,也有其他方式,例如Access-Control-Allow-HeadersAccess-Control-Allow-Methods,可以定義接受哪些 Request Header 以及接受哪些 Method。
  • 總結,如果想要發起跨來源 HTTP 請求並且順利收到回應的話,需要確保 Server 端有加上Access-Control-Allow-Origin,不然 Response 會被瀏覽器給擋下來並且顯示出錯誤訊息。

同源政策範例:

公司內部有一個 API 是拿來刪除文章的,只要把文章 id 用 POST 帶過去即可刪除。

舉例來說:POST https://lidemy.com/deletePost 並帶上 id=13,就會刪除 id 是 13 的文章。

公司前後端的網域是不同的,而且後端並沒有加上 CORS 的 header,因此小明認為前端用 ajax 會受到同源政策的限制,request 根本發不出去,所以前端沒辦法利用 ajax 呼叫這個 API 刪除文章。

請問小明的說法是正確的嗎?如果錯誤,請指出錯誤的地方。

答案:錯誤。

這題考的是你對同源政策的理解程度。

首先,==同源政策限制的對象有兩種,簡單請求跟非簡單請求==,前者像是 GET 跟 POST(還必須沒有加上任何 custom header),後者像是 DELETE 之類的。這邊可以自己去找 MDN 的參考資料來看。

簡單請求跟非簡單請求的差別在於:

==簡單請求限制的是「拿到 response」而不是「發出 request」,這是超級無敵重要的一點,但我想很多人都會搞混==

==非簡單請求會先發出一個 OPTIONS 的 request 去查看後端是否允許非同源的 request,如果不允許,則不會把請求發出去==。舉例來說,使用 DELETE 方法就會先發出 OPTIONS 的 request,確認後端有帶 CORS 的 header 才會把 DELETE 的 request 發出去。

所以,以這題的狀況來說,儘管後端沒有加上 CORS 的 header,刪除文章的 request 依舊發的出去,只是前端的 JS 拿不到 response,然後 console 會出現錯誤。所以文章還是被刪掉了,只是前端不知道到底有沒有成功。

參考資料:
輕鬆理解 Ajax 與跨來源請求
第十五週網站前後端開發基礎測試


綜合示範:抓取資料並顯示

  • 先切好要抓取的資料的 css 版面
  • 透過 Ajax 抓取資料
  • 然後用 innerHTML 插入處理好的 HTML 樣式,然後用${}插入抓取到的動態資料。
  • 最後使用 appendChild 來顯示到頁面上
  • 這種方式叫==client side rendering==,用 Javascript 去動態新增網頁內容,所以在檢視網頁原始碼的時候 body 內會沒東西,因為是透過 js 動態新增內容。
    ```javascript=

    const request = new XMLHttpRequest();
    const container = document.querySelector('.app')
    request.onload = function() {
    if (request.status >= 200 && request.status < 400) {
      const response = request.responseText;
      const json = JSON.parse(response);
      const users = json.data;
      for (let i = 0; i < users.length; i += 1) {
        const div = document.createElement('div')
        div.classList.add('profile');
        div.innerHTML = `
        <div class='profile__name' >${users[i].first_name} ${users[i].last_name}</div>
        <img class='profile__img' src='${users[i].avatar}' />
        `
        container.appendChild(div);
      }
    } else {
      console.log('err');
    }
    
    }

request.onerror = function() {
console.log('error');
}
request.open('GET', 'https://reqres.in/api/users', true);
// 如果 url 有變數,可以用'網址' + 變數
// 例如:'https://api.twitch.tv/kraken/streams/?game='+ data.top[0].game.name
request.send()

</script>
```

tags: Week8

#week8







Related Posts

How to solve the perpetual loading issue in Evernote? Evernote 一直轉圈圈的解決辦法

How to solve the perpetual loading issue in Evernote? Evernote 一直轉圈圈的解決辦法

建立 AWS EC2 Instance

建立 AWS EC2 Instance

React-[建立開發環境篇]-安裝vite & 將vite專案打包並部署

React-[建立開發環境篇]-安裝vite & 將vite專案打包並部署


Comments